﻿using System;
using System.Collections.Generic;
using System.Reflection;
using NsbExtensions.EndpointMetadata;
using NsbExtensions.Messages.Management.Monitoring;
using NsbExtensions.Utilities;
using NServiceBus;
using NServiceBus.Config;
using ObjectBuilder;

namespace NsbExtensions
{
    public abstract class MessagingEndpoint : BackgroundServiceBase, IMessagingEndpoint
    {
        private List<IBackgroundService> _localServices;
        private List<Assembly> _messageHandlerAssemblies;
        private EndpointOfflineEventArgs _pendingOfflineReason;
        private IEndpointDescription _metadata;


        public IBuilder Builder { get; set; }

        protected List<IBackgroundService> LocalServices
        {
            get { return _localServices; }
            set
            {
                if (_localServices == null)
                    _localServices = value;
                else if (value != null)
                    value.ForEach(AddLocalBackgroundService);
            }
        }

        protected List<Assembly> MessageHandlerAssemblies
        {
            get { return _messageHandlerAssemblies; }
            set
            {
                if (_messageHandlerAssemblies == null)
                    _messageHandlerAssemblies = value;
                else if (value != null)
                    value.ForEach(AddMessageHandlerAssembly);
            }
        }

        protected EndpointOfflineNotification PendingOfflineNotification { get; set; }

        #region IMessagingEndpoint Members

        public event EventHandler<EndpointEventArgs> EndpointOnline;
        public event EventHandler<EndpointOfflineEventArgs> EndpointOffline;


        public IBus Bus { get; protected set; }


        public IEndpointDescription Metadata
        {
            get
            {
                if (_metadata == null)
                    _metadata = new EndpointDescription
                                    {
                                        EndpointID = "UNCONFIGURED_ENDPOINT"
                                    };
                return _metadata;
            }
            protected set { _metadata = value; }
        }

        public void TakeEndpointOffline(string reason, TimeSpan outageDuration)
        {
            _pendingOfflineReason = new EndpointOfflineEventArgs(Metadata.EndpointID, reason,
                                                                 Convert.ToInt32(outageDuration.TotalMinutes));
            Stop();
        }

        public void Restart(string reason)
        {
            string offlineReason = "Restarting";
            if (!String.IsNullOrEmpty(reason))
                offlineReason += ". Reason: " + reason;

            TakeEndpointOffline(offlineReason, TimeSpan.FromMinutes(1));

            Start();
        }

        #endregion

        public void AddLocalBackgroundService(IBackgroundService backgroundService)
        {
            if (IsRunning)
                throw new InvalidOperationException(
                    "You cannot add a local background service while the endpoint is running. Please stop the endpoint prior to adding the background service, or add the service prior to calling start for the first time.");


            if (backgroundService == null)
                throw new ArgumentNullException("backgroundService");

            if (LocalServices == null)
                LocalServices = new List<IBackgroundService>();

            if (!LocalServices.Contains(backgroundService))
                LocalServices.Add(backgroundService);
        }

        public void AddMessageHandlerAssembly(Assembly messageHandlerAssembly)
        {
            if (IsRunning)
                throw new InvalidOperationException(
                    "You cannot register message handlers while the endpoint is running. Please stop the endpoint prior to adding message handler assemblies, or add all assemblies prior to starting the service for the first time.");

            if (messageHandlerAssembly == null)
                throw new ArgumentNullException("messageHandlerAssembly");

            if (MessageHandlerAssemblies == null)
                MessageHandlerAssemblies = new List<Assembly>();
            if (!MessageHandlerAssemblies.Contains(messageHandlerAssembly))
                MessageHandlerAssemblies.Add(messageHandlerAssembly);
        }

        protected override void StartService()
        {
            IBuilder builder = Builder;

            //Bus = builder.Build<IBus>();

            List<Assembly> assemblies = MessageHandlerAssemblies;
            if (assemblies != null)
            {
                //This is required for the IoC container to correctly instantiate
                //message handlers and sagas
                Configure.With(builder).SagasAndMessageHandlersIn(assemblies.ToArray());

                //RegisterMessagesAndHandlersIn needs to be overriden in the implementing class
                //because this ability is not part of IBus but of each
                //Bus implementation
                assemblies.ForEach(RegisterMessagesAndHandlersIn);
            }

            //We'll start the bus before registering resident local 
            //background services in case the services want to publish any messages.
            Bus.Start();

            //Start any background services that will be running,
            //such as a heartbeat/status publisher.
            if (LocalServices == null)
                LocalServices = new List<IBackgroundService>();
            RegisterLocalBackgroundServices(LocalServices, builder);
            foreach (IBackgroundService localService in LocalServices)
            {
                //Let the service know who'se the boss around here, if it cares.
                if (localService is IEndpointService)
                    ((IEndpointService) localService).Endpoint = this;

                localService.Start();
            }

            OnEndpointOnline();
        }

        protected override void ShutDownService()
        {
            //If a shutdown was initiated manually with a reason provided,
            //we'll use it. Otherwise, we don't know why we're shutting down,
            //so we need a generic reason.
            EndpointOfflineEventArgs eventArgs = _pendingOfflineReason ??
                                                 new EndpointOfflineEventArgs(Metadata.EndpointID,
                                                                              "General Server Shutdown", -1);

            OnEndpointOffline(eventArgs);

            //We don't need this anymore, and we don't
            //want it used again.
            _pendingOfflineReason = null;

            //Let's lock up and go home. See everyone in the morning.
            if (LocalServices != null)
            {
                foreach (IBackgroundService localService in LocalServices)
                {
                    try
                    {
                        localService.Stop();
                    }
                    catch (Exception ex)
                    {
                        ex =
                            new Exception(
                                "There was a problem shutting down a local service (" + localService.GetType().Name +
                                ") : " + ex.Message, ex);
                        OnServiceException(ex);
                    }
                }
            }
        }

        protected virtual void RegisterLocalBackgroundServices(List<IBackgroundService> localServiceRegistry,
                                                               IBuilder builder)
        {
            //can be overriden to provide local services.
        }

        protected virtual void OnEndpointOnline()
        {
            EventHandler<EndpointEventArgs> evt = EndpointOnline;
            if (evt != null)
                evt(this, new EndpointEventArgs(Metadata.EndpointID));
        }

        protected virtual void OnEndpointOffline(EndpointOfflineEventArgs args)
        {
            EventHandler<EndpointOfflineEventArgs> evt = EndpointOffline;
            if (evt != null)
                evt(this, args);
        }

        /// <summary>
        /// Ensure that the bus implementation being used
        /// by this endpoint has an opportunity to extract
        /// any MessageHandler or Saga types out of the provided assembly.
        /// </summary>
        /// <param name="asm"></param>
        protected abstract void RegisterMessagesAndHandlersIn(Assembly asm);
    }
}